#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "map_utils.h"
#include "net_defs.h"

extern char* optarg;
extern int optind, optopt, opterr;

#define RULE_FILE ".rule"

int print_rule()
{
    FILE* fp = fopen(RULE_FILE, "r");
    if (!fp) {
        perror("fopen");
        return -1;
    }
    char buff[LINE_MAX];
    while (!feof(fp)) {
        bzero(buff, LINE_MAX);
        fgets(buff, LINE_MAX, fp);

        printf("%s", buff);
    }
    fclose(fp);
    return EXIT_SUCCESS;
}

int save_rule(int argc, char** argv)
{
    FILE* fp = fopen(RULE_FILE, "a");
    if (!fp) {
        perror("fopen");
        return -1;
    }

    char buff[LINE_MAX];
    bzero(buff, LINE_MAX);
    int i;
    for (i = 0; i < argc; i++) {
        // printf("%s ", argv[i]);
        strcat(buff, argv[i]);
        strcat(buff, " ");
    }
    // printf("\n");

    int len = strlen(buff);
    if (len > 0 && ' ' == buff[len - 1]) {
        buff[len - 1] = '\n';
    }

    fputs(buff, fp);
    fclose(fp);

    return EXIT_SUCCESS;
}

pkt_metadata_t* parse_args(int argc, char** argv)
{
    int opt = 0;
    int option_index = 0;
    optind = 0;
    opterr = 0;
    optopt = 0;
    optarg = NULL;

    if (argc < 2) {
        fprintf(stderr, "Usage: %s [s:d:l:n:p:j:h:v]\n", argv[0]);
        return NULL;
    }

    struct option long_options[] = {
        { "src", required_argument, NULL, 's' },
        { "dst", required_argument, NULL, 'd' },
        { "sport", required_argument, NULL, 'm' },
        { "dport", required_argument, NULL, 'n' },
        { "proto", required_argument, NULL, 'p' },
        { "action", required_argument, NULL, 'j' },
        { "help", no_argument, NULL, 'h' },
        { "log", no_argument, NULL, 'v' },
        { 0, 0, 0, 0 },
    };

    pkt_metadata_t* pkt_md = malloc(sizeof(pkt_metadata_t));
    memset(pkt_md, 0, sizeof(pkt_metadata_t));
    while ((opt = getopt_long(argc, argv, "s:d:m:n:p:j:hv", long_options, &option_index)) != -1) {
        switch (opt) {
            case 's':
                pkt_md->src = strdup(optarg);
                break;
            case 'd':
                pkt_md->dst = strdup(optarg);
                break;
            case 'm': {
                if (optarg) {
                    char* sport = strdup(optarg);
                    char* tok = strtok(sport, ",");
                    while (tok) {
                        int i = 0;
                        while (pkt_md->sport[i])
                            i++;
                        pkt_md->sport[i] = atoi(tok);
                        tok = strtok(NULL, ",");
                    }
                }
            } break;
            case 'n': {
                if (optarg) {
                    char* dport = strdup(optarg);
                    char* tok = strtok(dport, ",");
                    while (tok) {
                        int i = 0;
                        while (pkt_md->dport[i])
                            i++;
                        pkt_md->dport[i] = atoi(tok);
                        tok = strtok(NULL, ",");
                    }
                }
            } break;
            case 'p':
                pkt_md->proto = get_proto(optarg);
                break;
            case 'j':
                pkt_md->action = get_action(optarg);
                break;
            case 'v':
                print_rule();
                return NULL;
                break;
            default: /* '?' */
                fprintf(stderr, "Usage: %s [s:d:m:n:p:j:hv]\n", argv[0]);
                return NULL;
        }
    }

    return pkt_md;
}

pkt_md_list* load_pkg_metadata()
{
    int rules_num = 0;
    FILE* fp = fopen(RULE_FILE, "r");
    if (!fp) {
        perror("fopen");
        return NULL;
    }

    pkt_md_list* list = NULL;
    char buff[LINE_MAX];
    while (!feof(fp)) {
        bzero(buff, LINE_MAX);
        fgets(buff, LINE_MAX, fp);
        int len = strlen(buff);
        if (len > 0 && '\n' == buff[len - 1]) {
            buff[len - 1] = '\0';
        }

        if (strlen(buff) > 0) {
            // puts(buff);
            rules_num++;
            int argc = 0;
            char* argv[20] = { 0 };
            char* tok = strtok(buff, " ");
            while (tok) {
                argv[argc] = tok;
                argc++;
                tok = strtok(NULL, " ");
            }

            pkt_metadata_t* pkt_md = parse_args(argc, argv);
            // print_pkt_md(pkt_md, "parse");
            if (pkt_md) {
                pkt_md_node_t* node = (pkt_md_node_t*)malloc(sizeof(pkt_md_node_t));
                memset(node, 0, sizeof(pkt_md_node_t));
                node->rule_id = 0x01 << (rules_num - 1);
                node->pkt_md = pkt_md;

                if (!list) {
                    list = node;
                } else {
                    pkt_md_node_t* p = list;
                    while (p->next) {
                        p = p->next;
                    }
                    p->next = node;
                }
            }
        }
    }
    fclose(fp);
    return list;
}

int main(int argc, char** argv)
{
    pkt_metadata_t* pkt_md = parse_args(argc, argv);
    if (!pkt_md) {
        return -1;
    }
    print_pkt_md(pkt_md, "input");

    save_rule(argc, argv);

    pkt_md_list* list = load_pkg_metadata();
    if (!list) {
        printf("%s: load_pkg_metadata failed!\n", __FUNCTION__);
        return -1;
    }

    hash_map map_src, map_dst, map_sport, map_dport, map_action, map_proto;

    map_init(&map_src, str_hash, str_equal_f, MAP_CAPACTIY);
    map_init(&map_dst, str_hash, str_equal_f, MAP_CAPACTIY);
    map_init(&map_sport, int_hash, int_equal_f, MAP_CAPACTIY);
    map_init(&map_dport, int_hash, int_equal_f, MAP_CAPACTIY);
    map_init(&map_action, int_hash, int_equal_f, MAP_CAPACTIY);
    map_init(&map_proto, int_hash, int_equal_f, MAP_CAPACTIY);

    pkt_md_node_t* p = list;
    while (p) {
        uint32_t rule_id = p->rule_id;
        pkt_metadata_t* pkt_md = p->pkt_md;

        if (pkt_md->src) {
            map_put_str_xor(&map_src, pkt_md->src, rule_id);
        }

        if (pkt_md->dst) {
            map_put_str_xor(&map_dst, pkt_md->dst, rule_id);
        }

        int i = 0;
        if (pkt_md->sport[i]) {
            while (pkt_md->sport[i]) {
                map_put_int_xor(&map_dport, pkt_md->sport[i], rule_id);
                i++;
            }
        }

        i = 0;
        if (pkt_md->dport[i]) {
            while (pkt_md->dport[i]) {
                map_put_int_xor(&map_dport, pkt_md->dport[i], rule_id);
                i++;
            }
        }

        if (pkt_md->proto) {
            map_put_int_xor(&map_proto, pkt_md->proto, rule_id);
        }

        if (pkt_md->action) {
            map_put_int(&map_action, rule_id, pkt_md->action);
        }
        p = p->next;
    }

    print_str_map(&map_src, "-- src map");
    print_str_map(&map_dst, "-- dst map");
    print_int_map(&map_sport, "-- sport map");
    print_int_map(&map_dport, "-- dport map");
    print_int_map(&map_proto, "-- proto map");

    if (map_src.used > 0) {
        map_entry* el = map_get(&map_src, "others");
        if (!el) {
            map_put_str(&map_src, "others", 0);
        }
    }
    if (map_dst.used > 0) {
        map_entry* el = map_get(&map_dst, "others");
        if (!el) {
            map_put_str(&map_dst, "others", 0);
        }
    }

    p = list;
    while (p) {
        uint32_t rule_id = p->rule_id;
        pkt_metadata_t* pkt_md = p->pkt_md;

        if (!pkt_md->src) {
            map_str_xor_for_each(&map_src, rule_id);
        }

        if (!pkt_md->dst) {
            map_str_xor_for_each(&map_dst, rule_id);
        }

        if (!pkt_md->sport[0]) {
            map_int_xor_for_each(&map_sport, rule_id);
        }

        if (!pkt_md->dport[0]) {
            map_int_xor_for_each(&map_dport, rule_id);
        }

        if (!pkt_md->proto) {
            map_int_xor_for_each(&map_proto, rule_id);
        }

        p = p->next;
    }

    print_str_map(&map_src, "++ src map final");
    print_str_map(&map_dst, "++ dst map final");
    print_int_map(&map_sport, "++ sport map final");
    print_int_map(&map_dport, "++ dport map final");
    print_int_map(&map_proto, "++ proto map final");

    save_str_map(&map_src, SRC_MAP_PATH);
    save_str_map(&map_dst, DST_MAP_PATH);
    save_int_map(&map_sport, SPORT_MAP_PATH);
    save_int_map(&map_dport, DPORT_MAP_PATH);
    save_int_map(&map_proto, PROTO_MAP_PATH);
    save_int_map(&map_action, ACTION_MAP_PATH);

    return EXIT_SUCCESS;
}